Option Explicit

'################################################################
' Discriptor
' 二维数据对象
'################################################################

Private Const RANDOM_ID_SIZE As Integer = 15

Implements IDataFrame

'################################################################
' Field
'################################################################

' 被代理的二维数据对象
Private delegatedDF As IDataFrame

' 这里不单独创建真实 Series 的切片对象！！！！！
' 以防止多列多行时，产生的大量用来保存索引的 Long[] 数组
'
' 切片行索引-->真实DF行索引映射
' 获取行的方式 delegatedDF.getData (fieldRowIndexes(i)))
Private fieldRowIndexes() As Long

' 切片列索引-->真实DF列索引映射
Private fieldColumnIndexes() As Integer

' 暂不考虑使用【真实DF列索引-->切片列索引的映射】的映射，这会造成 Map 的两次搜索
' 真实DF列索引-->切片列索引的映射
' Map<RealColIndex, SliceColIndex>
'
' 真实列名-->切片列索引的映射
' 发生删除操作时，需要重新设置
' Map<colName, colIndex>
Private columnNameIndexMap As Object

' 当前 dataFrame 的Id
Private fieldId As String

' 行数
Private fieldSize As Long

' 列数
Private fieldColCount As Long

'################################################################
' Constructor
'################################################################
Private Sub Class_Initialize()
End Sub

Private Sub Class_Terminate()
End Sub

'################################################################
' Init
'################################################################
' @param df 原始 DataFrame
' @optional-param rowStart 行起始位置
' @optional-param rowStop  行终止位置(最终不会包含 stop 的索引)
' @optional-param rowStep  行起始位置
' @optional-param colStart 列起始位置
' @optional-param colStop 列终止位置(最终不会包含 stop 的索引)
' @optional-param colStep 列起始位置
' @optional-param rowIndexes 行索引数组
'                            如果使用了 rowIndexes, 将会忽略 rowStart + rowStop + rowStep 这三个参数
' @optional-param colIndexes 列索引数组（可以使用数值索引，也可以使用列名字符串）
'                            如果使用了 rowIndexes, 将会忽略 colStart + colStop + colStep 这三个参数
'                            如果使用了列名，但是列名不存在，将会抛出异常
Public Sub Init( _
    ByRef df As IDataFrame, _
    Optional ByRef rowStart As Variant, _
    Optional ByRef rowStop As Variant, _
    Optional ByRef rowStep As Variant=1, _
    Optional ByRef colStart As Variant, _
    Optional ByRef colStop As Variant, _
    Optional ByRef colStep As Variant=1, _
    Optional ByRef rowIndexes As Variant, _
    Optional ByRef colIndexes As Variant _
)
    ' 1. 保存 DF 引用
    Set delegatedDF = df

    ' 2. 计算需要使用的行索引
    Dim i As Long
    Dim j As Long

    If IsArray(rowIndexes) Then
        ' 2.1 如果使用 rowIndexes (必须是数组)，则使用
        ' 2.1.1 计算并保存行索引数量
        Dim ridxMin As Long
        Dim ridxMax As Long
        Dim ridxSize As Long
        ridxMin = LBound(rowIndexes)
        ridxMax = UBound(rowIndexes)
        ridxSize = ridxMax - ridxMin + 1

        fieldSize = ridxSize

        ' 保存各行索引的引用
        ' (如果行索引数量为 0 ，则表示当前暂时不引用任何行)
        If ridxSize > 0 Then
            Dim realRowIndex As Long
            Dim delegateDFRowSize As Long
            delegateDFRowSize = delegatedDF.Size

            ReDim Preserve fieldRowIndexes(0 To (fieldSize - 1))

            For i = 0 To (fieldSize - 1)
                realRowIndex = rowIndexes(ridxMin + i)

                ' 2.1.2 检查各行索引是否在可用范围内
                If realRowIndex < 0 Then
                    Err.Raise 10220, Description:="DataFrameSlice.Init: parameter rowIndexes(" & (ridxMin + i) & ")=" & realRowIndex & " out of bounds. min index=0, max index=" & (delegateDFRowSize - 1)
                End If

                If realRowIndex >= delegateDFRowSize Then
                    Err.Raise 10220, Description:="DataFrameSlice.Init: parameter rowIndexes(" & (ridxMin + i) & ")=" & realRowIndex & " out of bounds. min index=0, max index=" & (delegateDFRowSize - 1)
                End If

                ' 2.1.3 保存各行索引的引用
                fieldRowIndexes(i) = realRowIndex
            Next
        End If
    Else
        ' 2.2 否则使用 rowStart + rowStop + rowStep 计算切片信息
        ' 2.2.1 计算切片信息
        Dim rowSliceInfo As Variant
        rowSliceInfo = UtilsArray.computeArraySliceInfo( _
            delegatedDF.Size, _
            rowStart, _
            rowStop, _
            rowStep)

        ' 2.2.2 保存行数
        fieldSize = rowSliceInfo(3)

        ' 2.2.3 计算引用的索引（如果切片的计算结果中没有数据，则不计算）
        If fieldSize > 0 Then
            Dim realRowStartIdx As Long
            Dim realRowStopIdx As Long
            Dim realRowStep As Long
            realRowStartIdx = rowSliceInfo(0)
            realRowStopIdx = rowSliceInfo(1)
            realRowStep = rowSliceInfo(2)

            ReDim Preserve fieldRowIndexes(0 To (fieldSize - 1))

            j = 0
            i = realRowStartIdx
            If realRowStep < 0 Then
                While i > realRowStopIdx
                    fieldRowIndexes(j) = i
                    j = j + 1
                    i = i + realRowStep
                Wend
            Else
                While i < realRowStopIdx
                    fieldRowIndexes(j) = i
                    j = j + 1
                    i = i + realRowStep
                Wend
            End If
        End If
    End If

    ' 3. 计算需要使用的列索引
    Dim x As Integer
    Dim y As Integer

    If IsArray(colIndexes) Then
        ' 3.1 如果使用 colIndexes (必须是数组)，则使用
        ' 3.1.1 计算并保存列索引数量
        Dim cidxMin As Integer
        Dim cidxMax As Integer
        Dim cidxSize As Integer
        cidxMin = LBound(colIndexes)
        cidxMax = UBound(colIndexes)
        cidxSize = cidxMax - cidxMin + 1

        fieldColCount = cidxSize

        ' 保存各列索引的引用
        ' (如果行索引数量为 0 ，则表示当前暂时不引用任何行)
        If cidxSize > 0 Then
            ' 3.1.2 计算真实的列索引，并保存各个索引的引用
            ReDim Preserve fieldColumnIndexes(0 To (fieldColCount - 1))

            Dim colIndexVal As Variant
            For x = 0 To (fieldColCount - 1)
                colIndexVal = colIndexes(cidxMin + x)

                If VarType(colIndexVal) = vbString Then
                    ' 如果索引是字符串，则作为列名使用
                    If Not delegatedDF.hasColumn(colIndexVal) Then
                        Err.Raise 10214, Description:="DataFrameSlice.Init: can not find column name[" & colIndexVal & "]"
                    End If

                    fieldColumnIndexes(x) = delegatedDF.colNameToIndex(colIndexVal & "")
                ElseIf IsNumeric(colIndexVal) Then
                    ' 作为列索引使用
                    colIndexVal = Int(colIndexVal)
                    If colIndexVal < 0 Or colIndexVal >= delegatedDF.ColumnCount Then
                        Err.Raise 10210, Description:="DataFrameSlice.Init: parameter cIndex=" & colIndexVal & " out of bounds. min index=0, max index[" & (delegatedDF.ColumnCount - 1) & "]"
                        Exit Sub
                    End If

                    fieldColumnIndexes(x) = colIndexVal
                Else
                    Err.Raise 10012, Description:="DataFrameSlice.Init: please user Integer Or String for parameter"
                End If
            Next
        End If
    Else
        ' 3.2 否则使用 colStart + colStop + colStep 计算切片信息
        ' 3.2.1 计算切片信息
        Dim colSliceInfo As Variant
        colSliceInfo = UtilsArray.computeArraySliceInfo( _
            delegatedDF.ColumnCount, _
            colStart, _
            colStop, _
            colStep)

        ' 3.2.2 保存行数
        fieldColCount = colSliceInfo(3)

        ' 3.2.3 计算引用的索引（如果切片的计算结果中没有数据，则不计算）
        If fieldColCount > 0 Then
            Dim realColStartIdx As Long
            Dim realColStopIdx As Long
            Dim realColStep As Long
            realColStartIdx = colSliceInfo(0)
            realColStopIdx = colSliceInfo(1)
            realColStep = colSliceInfo(2)

            ReDim Preserve fieldColumnIndexes(0 To (fieldColCount - 1))

            y = 0
            x = realColStartIdx
            If realColStep < 0 Then
                While x > realColStopIdx
                    fieldColumnIndexes(y) = x
                    y = y + 1
                    x = x + realColStep
                Wend
            Else
                While x < realColStopIdx
                    fieldColumnIndexes(y) = x
                    y = y + 1
                    x = x + realColStep
                Wend
            End If
        End If
    End If

    ' 3.3 计算【真实列名-->切片列索引的映射】
    Set columnNameIndexMap = UtilsCreateSysObj.createMap
    For x = 0 To (fieldColCount - 1)
        columnNameIndexMap.Add delegatedDF.colIndexToName(fieldColumnIndexes(x)), x
    Next

    ' 4. 生成随机ID
    fieldId = UtilsRandom.enNumStr(RANDOM_ID_SIZE)
End Sub

' 创建包含全列的空行引用对象
' @param df 原始 DataFrame
' @param initRowSize 初始化行缓存长度，默认=10
Public Sub InitEmptyRowRef(ByRef df As IDataFrame, Optional ByRef initRowSize As Long = 10)
    ' 1. 保存 DF 引用
    Set delegatedDF = df

    ' 2. 初始化行引用缓存
    fieldSize = 0
    ReDim fieldRowIndexes(0 To (initRowSize - 1))

    ' 3. 保存列数
    fieldColCount = df.ColumnCount

    ' 4. 创建所有的列引用
    ' 4.1 建立: 切片列索引-->真实DF列索引映射
    ReDim Preserve fieldColumnIndexes(0 To (fieldColCount - 1))

    Dim i As Integer
    For i = 0 To (fieldColCount - 1)
        fieldColumnIndexes(i) = i
    Next

    ' 4.2 计算【真实列名-->切片列索引的映射】
    Set columnNameIndexMap = UtilsCreateSysObj.createMap
    For i = 0 To (fieldColCount - 1)
        columnNameIndexMap.Add delegatedDF.colIndexToName(fieldColumnIndexes(i)), i
    Next

    ' 5. 生成随机ID
    fieldId = UtilsRandom.enNumStr(RANDOM_ID_SIZE)
End Sub

'################################################################
' Property
'################################################################
' 获取当前 DataFrame 的最大行数
Public Property Get IDataFrame_Size() As Long
    IDataFrame_Size = fieldSize
End Property

' 获取列数
Public Property Get IDataFrame_ColumnCount() As Integer
    IDataFrame_ColumnCount = fieldColCount
End Property

' 获取所有列名
Public Property Get IDataFrame_ColumnNames() As Collection
    Dim result As Collection
    Set result = New Collection

    Dim k As Variant
    For Each k In columnNameIndexMap.Keys
        result.Add k
    Next

    Set IDataFrame_ColumnNames = result
End Property

' 获取当前 DataFrame 的 Id
Public Property Get IDataFrame_ID() As String
    IDataFrame_ID = fieldId
End Property

' 设置当前 DataFrame 的 Id
Public Property Let IDataFrame_ID(ByRef val As String)
    fieldId = val
End Property

'################################################################
' Unsafe Function
' 外部禁止使用
'################################################################
' 获取被代理的 DF 中的真实列对象
' @param cindex 列的索引（可以使用列索引，也可以使用列名）
' @return IDFSeries
Public Function unsafeGetDelegatedCol(ByRef cindex As Variant) As IDFSeries
    Set unsafeGetDelegatedCol = delegatedDF.getCol(cindex)
End Function

'################################################################
' Function
'################################################################
'--------------------------------------------------
' 手动添加索引引用
'--------------------------------------------------
' 向当前 `DataFrameSlice` 对象的末尾添加一个原始 Series 的索引引用
'
' @param originIndex 原始 Series 的索引
Public Sub appendRef(ByRef originIndex As Long)
    ' 1. 检查索引是否存在，如果不存在需要抛出异常
    If originIndex < 0 Then
        Err.Raise 10110, Description:="parameter originIndex=" & originIndex & " out of bounds. delegatedSeries's min index=0, max index[" & (delegatedDF.Size - 1) & "]"
        Exit Sub
    ElseIf originIndex >= delegatedDF.Size Then
        Err.Raise 10110, Description:="parameter originIndex=" & originIndex & " out of bounds. delegatedSeries's min index=0, max index[" & (delegatedDF.Size - 1) & "]"
        Exit Sub
    End If

    ' 2. 检查数组范围
    '    如果数组已满，则进行扩容，每次扩容为原来的 1.5 倍
    If UBound(fieldRowIndexes) <= (fieldSize - 1) Then
        ReDim Preserve fieldRowIndexes(0 To (CLng(fieldSize * 1.5) - 1))
    End If

    ' 3. 保存引用
    fieldRowIndexes(fieldSize) = originIndex
    fieldSize = fieldSize + 1
End Sub

'--------------------------------------------------
' 行操作
'--------------------------------------------------
' 添加指定数量的空行
' @param count 需要添加的行数，默认为 1 行
Public Sub IDataFrame_appendEmptyRow(Optional count As Long = 1)
    ' 1. 如果 count 是 0，则不执行任何操作
    If count <= 0 Then
        Exit Sub
    End If

    ' 2. 检查引用缓存的范围
    '    如果数组已满，则进行扩容，每次扩容为原来的 1.5 倍
    If UBound(fieldRowIndexes) <= (fieldSize - 1) Then
        ReDim Preserve fieldRowIndexes(0 To (CLng(fieldSize * 1.5) - 1))
    End If

    ' 3. 保存引用
    '    从 fieldSize 到 (fieldSize + count - 1)
    Dim i As Integer
    Dim delegatedDFSize As Long
    delegatedDFSize = delegatedDF.Size
    For i = 0 To (count - 1)
        fieldRowIndexes(fieldSize + i) = delegatedDFSize + i
    Next

    ' 4. 重新计算当前切片内的引用数量
    fieldSize = fieldSize + count

    ' 5. 在原始 DF 上增加空行
    delegatedDF.appendEmptyRow count
End Sub

' 新增一行，并拷贝指定行的数据到这个新行
' @param index 需要拷贝的数据行的索引
' @optional-param needDescriptor 如果数据存在描述符，是否需要拷贝，默认=False
Public Sub IDataFrame_copyAndAppendRow(ByRef index As Long, Optional ByRef needDescriptor As Boolean=False)
    ' 1. 检查行索引的范围
    If index < 0 Or index >= fieldSize Then
        Err.Raise 10114, Description:="parameter index=" & index & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
        Exit Sub
    End If

    ' 2. 检查引用缓存的范围
    '    如果数组已满，则进行扩容，每次扩容为原来的 1.5 倍
    If UBound(fieldRowIndexes) <= (fieldSize - 1) Then
        ReDim Preserve fieldRowIndexes(0 To (CLng(fieldSize * 1.5) - 1))
    End If

    ' 3. 保存引用
    '    从 fieldSize 到 (fieldSize + count - 1)
    fieldRowIndexes(fieldSize) = delegatedDF.Size

    ' 4. 重新计算当前切片内的引用数量
    fieldSize = fieldSize + 1

    ' 5. 在原始 DF 上完成拷贝
    delegatedDF.copyAndAppendRow fieldRowIndexes(index), needDescriptor
End Sub

' 直接添加一行数据
' 如果参数数量为 0，则不会执行添加的操作
' 如果参数数量比列的数量少，则剩余的列设置为 Empty
' 如果参数数量比列的数量多，则多余的参数会被忽略
'
' @param colDatas 这一行的每一列的数据
Public Sub IDataFrame_appendRowByColDatas(ParamArray colDatas() As Variant)
    Dim cdArr As Variant
    cdArr = colDatas
    IDataFrame_appendRowByColDataArray cdArr
End Sub

' 直接添加一行数据
' 如果参数数量为 0，则不会执行添加的操作
' 如果参数数量比列的数量少，则剩余的列设置为 Empty
' 如果参数数量比列的数量多，则多余的参数会被忽略
'
' @param colDataArray 这一行的每一列的数据
Public Sub IDataFrame_appendRowByColDataArray(ByRef colDataArray As Variant)
    ' 1. 如果没有有效参数，则立刻停止
    Dim idxMax As Integer
    Dim idxMin As Integer
    Dim idxCount As Integer
    idxMin = LBound(colDataArray)
    idxMax = UBound(colDataArray)
    idxCount = idxMax - idxMin + 1
    If idxCount = 0 Then
        Exit Sub
    End If

    ' 2. 检查引用缓存的范围
    '    如果数组已满，则进行扩容，每次扩容为原来的 1.5 倍
    If UBound(fieldRowIndexes) <= (fieldSize - 1) Then
        ReDim Preserve fieldRowIndexes(0 To (CLng(fieldSize * 1.5) - 1))
    End If

    ' 3. 保存引用
    fieldRowIndexes(fieldSize) = delegatedDF.Size

    ' 4. 在原始 DF 上增加空行
    delegatedDF.appendEmptyRow

    ' 5. 确认需要写入的数据数量
    Dim colCount As Integer
    If idxCount < fieldColCount Then
        colCount = idxCount
    Else
        colCount = fieldColCount
    End If

    ' 6. 写入数据
    Dim i As Long
    For i = 0 To (colCount - 1)
        delegatedDF.setDataByColIndex _
            fieldRowIndexes(fieldSize), _
            fieldColumnIndexes(i), _
            colDataArray(idxMin + i)
    Next

    ' 7. 增加行数
    fieldSize = fieldSize + 1
End Sub

' 删除指定索引的行
' 只删除当前对象内的数据
'
' @param index 需要删除的列的索引
'                 如果超出了索引范围，将会抛出异常
Public Sub IDataFrame_delRow(ParamArray indexes() As Variant)
    Dim ies As Variant
    ies = indexes
    IDataFrame_delRowByIndexArray ies
End Sub

' 删除指定索引的行 (使用数组)
' 只删除当前对象内的数据
'
' @param index 需要删除的列的索引
'                 如果超出了索引范围，将会抛出异常
Public Sub IDataFrame_delRowByIndexArray(ByRef indexes As Variant)
    ' 1. 如果没有有效参数，则立刻停止
    Dim idxMax As Integer
    Dim idxMin As Integer
    Dim idxSize As Integer
    idxMin = LBound(indexes)
    idxMax = UBound(indexes)
    idxSize = idxMax - idxMin + 1
    If idxSize = 0 Then
        Exit Sub
    End If

    ' 2. 删除当前切片中的引用
    ' 2.1 检查所有索引的范围
    Dim i As Long
    Dim indexVal As Long
    For i = idxMin To idxMax
        indexVal = Int(indexes(i))
        If indexVal < 0 Or indexVal >= fieldSize Then
            Err.Raise 10213, Description:="parameter index(" & i & ")=" & indexVal & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
            Exit Sub
        End If
    Next

    ' 2.2 清除当前切片中的引用，并保存各引用对应的真实DF行索引
    For i = idxMin To idxMax
        indexVal = Int(indexes(i))
        ' 将引用位置设置为 -1 作为删除标记，用来在下一步移动数据
        fieldRowIndexes(indexVal) = -1
    Next

    ' 2.3 移动当前切片内的引用
    Dim j As Long
    i = -1
    For j = 0 To (fieldSize - 1)
        If fieldRowIndexes(j) <> -1 Then
            i = i + 1

            ' 如果是原地赋值，则跳过
            If i <> j Then
                fieldRowIndexes(i) = fieldRowIndexes(j)
            End If
        End If
    Next

    ' 2.4 重新计算当前切片中引用的数据量
    fieldSize = fieldSize - idxSize

    ' 2.5 移动数据后，清空 i -- fieldSize 的引用
    For j = i + 1 To (fieldSize - 1)
        fieldRowIndexes(j) = -1
    Next
End Sub

' 删除指定索引的行
' 如果当前是引用对象，也会直接删除原始对象中的数据
'
' @param index 需要删除的列的索引
'                 如果超出了索引范围，将会抛出异常
Public Sub IDataFrame_deepDelRow(ParamArray indexes() As Variant)
    Dim ies As Variant
    ies = indexes
    IDataFrame_deepDelRowByIndexArray ies
End Sub

' 删除指定索引的行 (使用数组)
'
' @param index 需要删除的列的索引
'                 如果超出了索引范围，将会抛出异常
Public Sub IDataFrame_deepDelRowByIndexArray(ByRef indexes As Variant)
    ' 1. 如果没有有效参数，则立刻停止
    Dim idxMax As Integer
    Dim idxMin As Integer
    Dim idxSize As Integer
    idxMin = LBound(indexes)
    idxMax = UBound(indexes)
    idxSize = idxMax - idxMin + 1
    If idxSize = 0 Then
        Exit Sub
    End If

    ' 2. 删除当前切片中的引用
    ' 2.1 检查所有索引的范围
    Dim i As Long
    Dim indexVal As Long
    For i = idxMin To idxMax
        indexVal = Int(indexes(i))
        If indexVal < 0 Or indexVal >= fieldSize Then
            Err.Raise 10213, Description:="parameter index(" & i & ")=" & indexVal & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
            Exit Sub
        End If
    Next

    ' 2.2 清除当前切片中的引用，并保存各引用对应的真实DF行索引
    Dim realDeletedIndex() As Long
    ReDim realDeletedIndex(0 To (idxSize - 1)) As Long
    For i = 0 To (idxSize - 1)
        indexVal = Int(indexes(idxMin + i))
        ' 保存各引用对应的真实DF行索引
        realDeletedIndex(i) = fieldRowIndexes(indexVal)
        ' 将引用位置设置为 -1 作为删除标记，用来在下一步移动数据
        fieldRowIndexes(indexVal) = -1
    Next

    ' 2.3 移动当前切片内的引用
    Dim j As Long
    i = -1
    For j = 0 To (fieldSize - 1)
        If fieldRowIndexes(j) <> -1 Then
            i = i + 1

            ' 如果是原地赋值，则跳过
            If i <> j Then
                fieldRowIndexes(i) = fieldRowIndexes(j)
            End If
        End If
    Next

    ' 2.4 重新计算当前切片中引用的数据量
    fieldSize = fieldSize - idxSize

    ' 2.5 移动数据后，清空 i -- fieldSize 的引用
    For j = i + 1 To (fieldSize - 1)
        fieldRowIndexes(j) = -1
    Next

    '-----------------------------------------------------
    ' 3. 清除被代理 DF 中的数据
    delegatedDF.deepDelRowByIndexArray realDeletedIndex
End Sub

'--------------------------------------------------
' 列操作
'--------------------------------------------------
' 检查是否存在指定的列
'
' @param cIndex 列的索引或列名
'               如果 index 不是数字(索引) 或 字符串(列名)，则抛出异常
Public Function IDataFrame_hasColumn(ByRef index As Variant) As Boolean
    ' 1. 获取真实的列索引
    If VarType(index) = vbString Then
        ' 如果索引是字符串，则作为列名使用
        If Not columnNameIndexMap.Exists(index) Then
            IDataFrame_hasColumn = False
            Exit Function
        End If
    ElseIf IsNumeric(index) Then
        ' 作为列索引使用
        Dim indexVal As Integer
        indexVal = Int(index)
        If indexVal < 0 Or indexVal >= fieldColCount Then
            IDataFrame_hasColumn = False
            Exit Function
        End If
    Else
        Err.Raise 10012, Description:="please user Integer Or String for parameter"
    End If

    IDataFrame_hasColumn = True
End Function

' 检查是否存在指定的列
' 如果当前是引用对象，也会向下搜索每一层引用对象中的列
'
' @param cIndex 列的索引或列名
'               如果 index 不是数字(索引) 或 字符串(列名)，则抛出异常
Public Function IDataFrame_deepHasColumn(ByRef index As Variant) As Boolean
    ' 1. 在当前切片引用内部检查，如果存在则直接返回 True。否则继续向引用 DF 中搜索
    If IDataFrame_hasColumn(index) Then
        IDataFrame_deepHasColumn = True
        Exit Function
    End If

    ' 2. 向引用 DF 中搜索列
    IDataFrame_deepHasColumn = delegatedDF.deepHasColumn(index)
End Function

' 将列索引转换成列名 (只限定在当前切片索引内引用的内容)
'
' @param cIndex 列的索引
'               如果超出了索引范围，将会抛出异常
Public Function IDataFrame_colIndexToName(ByRef cIndex As Integer) As String
On Error Goto catch:
    IDataFrame_colIndexToName = delegatedDF.colIndexToName(fieldColumnIndexes(cIndex))
    Exit Function
catch:
    ' 处理索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10210, Description:="parameter cIndex=" & cIndex & " out of bounds. min index=0, max index[" & (fieldColCount - 1) & "]"
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Function

' 将列名转换成列索引(只限定在当前切片索引内引用的内容)
'
' @param cName 列名
'              如果列名不存在，将会抛出异常
Public Function IDataFrame_colNameToIndex(ByRef cName As String) As Integer
    If columnNameIndexMap.Exists(cName) Then
        IDataFrame_colNameToIndex = columnNameIndexMap.Item(cName)
    Else
        Err.Raise 10214, Description:="can not find column name[" & cName & "]"
    End If
End Function

' 获取列对象
'
' @param index 列的索引（可以使用列索引，也可以使用列名）
'              如果超出了索引范围，将会抛出异常
'              如果列名不存在，将会抛出异常
Public Function IDataFrame_getCol(ByRef index As Variant) As IDFSeries
    ' 1. 获取真实的列索引
    Dim indexVal As Integer
    If VarType(index) = vbString Then
        ' 如果索引是字符串，则作为列名使用
        If Not columnNameIndexMap.Exists(index) Then
            Err.Raise 10214, Description:="can not find column name[" & index & "]"
        End If

        indexVal = columnNameIndexMap.Item(index)
    ElseIf IsNumeric(index) Then
        ' 作为列索引使用
        indexVal = Int(index)
        If indexVal < 0 Or indexVal >= fieldColCount Then
            Err.Raise 10210, Description:="parameter cIndex=" & indexVal & " out of bounds. min index=0, max index[" & (fieldColCount - 1) & "]"
            Exit Function
        End If
    Else
        Err.Raise 10012, Description:="please user Integer Or String for parameter"
    End If

    ' 2. 创建列对象的引用
    Dim result As DataFrameSliceColRef
    Set result = New DataFrameSliceColRef
    result.Init Me,indexVal
    Set IDataFrame_getCol = result
End Function

' 删除指定索引的列
' 只删除当前对象内的数据
'
' @param index 需要删除的列的索引（可以使用列索引，也可以使用列名）
'                 如果超出了索引范围，将会抛出异常
'                 如果列名不存在，将会抛出异常
Public Sub IDataFrame_delCol(ParamArray indexes() As Variant)
    Dim ies As Variant
    ies = indexes
    IDataFrame_delColByIndexArray ies
End Sub

' 删除指定索引的列 (使用数组)
' 只删除当前对象内的数据
'
' @param index 需要删除的列的索引（可以使用列索引，也可以使用列名）
'                 如果超出了索引范围，将会抛出异常
'                 如果列名不存在，将会抛出异常
Public Sub IDataFrame_delColByIndexArray(ByRef indexes As Variant)
    ' 1. 如果没有有效参数，则立刻停止
    Dim idxMax As Integer
    Dim idxMin As Integer
    Dim idxSize As Integer
    idxMin = LBound(indexes)
    idxMax = UBound(indexes)
    idxSize = idxMax - idxMin + 1
    If idxSize = 0 Then
        Exit Sub
    End If

    ' 2. 创建真实索引缓存
    '    如果下一步的解析中遇到了列名，则转换后进行保存
    Dim realColIndex() As Integer
    ReDim realColIndex(0 To (idxSize - 1)) As Integer

    ' 3. 检查每个索引的范围，并对各索引进行转换（列名 ---> 列索引）
    Dim i As Integer
    Dim indexVal As Variant
    For i = 0 To (idxSize - 1)
        indexVal = indexes(idxMin + i)

        If VarType(indexVal) = vbString Then
            ' 如果索引是字符串，则作为列名使用
            If Not columnNameIndexMap.Exists(indexVal) Then
                Err.Raise 10214, Description:="can not find column name[" & indexVal & "]"
            End If

            realColIndex(i) = columnNameIndexMap.Item(indexVal)
        ElseIf IsNumeric(indexVal) Then
            ' 作为列索引使用
            indexVal = Int(indexVal)
            If indexVal < 0 Or indexVal >= fieldColCount Then
                Err.Raise 10210, Description:="parameter cIndex=" & indexVal & " out of bounds. min index=0, max index[" & (fieldColCount - 1) & "]"
                Exit Sub
            End If

            realColIndex(i) = indexVal
        Else
            Err.Raise 10012, Description:="please user Integer Or String for param[" & (idxMin + i) & "]."
        End If
    Next

    ' 4. 删除列
    ' 4.1 从【切片列索引-->真实DF列索引映射】删除切片列索引
    For i = 0 To (idxSize - 1)
        ' 将需要删除的列引用置为 -1 作为标记
        fieldColumnIndexes(realColIndex(i)) = -1
    Next

    ' 4.2 清理列索引映射中的空槽位
    Dim j As Integer
    i = -1
    For j = 0 To (fieldColCount - 1)
        If fieldColumnIndexes(j) <> -1 Then
            i = i + 1

            If i <> j Then
                fieldColumnIndexes(i) = fieldColumnIndexes(j)
            End If
        End If
    Next

    ' 4.3 重新计算列数
    fieldColCount = fieldColCount - idxSize
    ReDim Preserve fieldColumnIndexes(0 To (fieldColCount - 1))

    ' 4.4 重新构造【真实列名-->切片列索引的映射】
    columnNameIndexMap.RemoveAll
    For i = 0 To (fieldColCount - 1)
        ' 将真实列索引转换成真实列名，并保存
        columnNameIndexMap.Add _
            delegatedDF.colIndexToName(fieldColumnIndexes(i)), _
            i
    Next
End Sub

' 删除指定索引的列
' 如果当前是引用对象，也会直接删除原始对象中的数据
'
' @param index 需要删除的列的索引（可以使用列索引，也可以使用列名）
'                 如果超出了索引范围，将会抛出异常
'                 如果列名不存在，将会抛出异常
Public Sub IDataFrame_deepDelCol(ParamArray indexes() As Variant)
    Dim ies As Variant
    ies = indexes
    IDataFrame_deepDelColByIndexArray ies
End Sub

' 删除指定索引的列 (使用数组)
' 如果当前是引用对象，也会直接删除原始对象中的数据
'
' @param index 需要删除的列的索引（可以使用列索引，也可以使用列名）
'                 如果超出了索引范围，将会抛出异常
'                 如果列名不存在，将会抛出异常
Public Sub IDataFrame_deepDelColByIndexArray(ByRef indexes As Variant)
    ' 1. 如果没有有效参数，则立刻停止
    Dim idxMax As Integer
    Dim idxMin As Integer
    Dim idxSize As Integer
    idxMin = LBound(indexes)
    idxMax = UBound(indexes)
    idxSize = idxMax - idxMin + 1
    If idxSize = 0 Then
        Exit Sub
    End If

    ' 2. 创建真实索引缓存
    '    如果下一步的解析中遇到了列名，则转换后进行保存
    Dim realColIndex() As Integer
    ReDim realColIndex(0 To (idxSize - 1)) As Integer

    ' 3. 检查每个索引的范围，并对各索引进行转换（列名 ---> 列索引）
    Dim i As Integer
    Dim indexVal As Variant
    For i = 0 To (idxSize - 1)
        indexVal = indexes(idxMin + i)

        If VarType(indexVal) = vbString Then
            ' 如果索引是字符串，则作为列名使用
            If Not columnNameIndexMap.Exists(indexVal) Then
                Err.Raise 10214, Description:="can not find column name[" & indexVal & "]"
            End If

            realColIndex(i) = columnNameIndexMap.Item(indexVal)
        ElseIf IsNumeric(indexVal) Then
            ' 作为列索引使用
            indexVal = Int(indexVal)
            If indexVal < 0 Or indexVal >= fieldColCount Then
                Err.Raise 10210, Description:="parameter cIndex=" & indexVal & " out of bounds. min index=0, max index[" & (fieldColCount - 1) & "]"
                Exit Sub
            End If

            realColIndex(i) = indexVal
        Else
            Err.Raise 10012, Description:="please user Integer Or String for param[" & (idxMin + i) & "]."
        End If
    Next

    ' 4. 抽取所有未被删除的列名
    ' 4.1 将需要删除的列设置成空槽位,并保存需要删除的列名
    Dim deleteColNames() As String
    ReDim deleteColNames(0 To (idxSize - 1)) As String

    For i = 0 To (idxSize - 1)
        ' 暂存需要删除的列名，用来删除真实列
        deleteColNames(i) = delegatedDF.colIndexToName(fieldColumnIndexes(realColIndex(i)))
        ' 将需要删除的列引用置为 -1 作为标记
        fieldColumnIndexes(realColIndex(i)) = -1
    Next

    ' 4.2 按照顺序抽取所有未被删除的列名
    Dim notDeleteNames() As String
    ReDim notDeleteNames(0 To (fieldColCount - idxSize - 1)) As String
    Dim j As Integer
    j = 0
    For i = 0 To (fieldColCount - 1)
        If fieldColumnIndexes(i) <> -1 Then
            notDeleteNames(j) = delegatedDF.colIndexToName(fieldColumnIndexes(i))
            j = j + 1
        End If
    Next

    ' 5. 删除引用 DF 中的列
    delegatedDF.deepDelColByIndexArray deleteColNames

    ' 6. 重新计算列数
    fieldColCount = fieldColCount - idxSize

    ' 7. 按照【未被删除的列名】重新构造 :
    ReDim fieldColumnIndexes(0 To (fieldColCount - 1))
    columnNameIndexMap.RemoveAll

    Dim delegateColIndex As Integer
    For i = 0 To (fieldColCount - 1)
        delegateColIndex = delegatedDF.colNameToIndex(notDeleteNames(i))
        ' 7.1 重新构造 【切片列索引-->真实DF列索引映射】
        fieldColumnIndexes(i) = delegateColIndex

        ' 7.2 重新构造 【真实列名-->切片列索引的映射】
        columnNameIndexMap.Add notDeleteNames(i), i
    Next
End Sub

' 向所有行的末尾添加一个新的列
' 如果 colName 在 DataFrame 中已经存在，则抛出异常
'
' @param colName 新列的列名
' @param dType 列的类型

Public Sub IDataFrame_addCol(ByRef colName As String, Optional ByRef dType As DF_DataType)
    ' 1. 检查列是否存在
    ' 1.1 如果列名在当前切片内已经存在，则抛出异常
    If columnNameIndexMap.Exists(colName) Then
        Err.Raise 10217, Description:="column name[" & colName & "] has been existed"
    End If

    ' 1.2 如果原始DF 中已经存在该列，则抛出异常
    If delegatedDF.deepHasColumn(colName) Then
        Err.Raise 10218, Description:="column name[" & colName & "] has been existed in nested DataFrame"
    End If

    ' 2. 向底层添加一个列
    delegatedDF.addCol colName, dType

    ' 3. 从底层 df 中获取新列对应的索引
    Dim newColIndex As Integer
    newColIndex = delegatedDF.colNameToIndex(colName)

    ' 4. 添加缓存
    ReDim fieldColumnIndexes(0 To fieldColCount)
    fieldColumnIndexes(fieldColCount) = newColIndex

    columnNameIndexMap.Add colName, fieldColCount

    ' 5. 重新计算列数
    fieldColCount = fieldColCount + 1
End Sub
'--------------------------------------------------
' 描述符操作
'--------------------------------------------------

' 获取指定行的描述器
' 如果不存在，则返回 Nothing
'
' @param index 行索引
' @optional-param needCreate 如果描述信息对象不存在是否需要进行创建。默认值=False
' @return IDFDescriptor 数据描述器
Public Function IDataFrame_getRowDescriptor(ByRef index As Long, Optional ByRef needCreate As Boolean = False) As IDFDescriptor
    ' 1. 检查索引范围
    If index < 0 Or index >= fieldSize Then
        Err.Raise 10213, Description:="parameter index=" & index & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
        Exit Function
    End If

    ' 2. 从底层 DF 中获取描述符
    Set IDataFrame_getRowDescriptor = delegatedDF.getRowDescriptor(fieldRowIndexes(index), needCreate)
End Function

' 获取指定行列确定的槽位的描述器
' 如果不存在，则返回 Nothing
'
' @param rindex 行索引
' @param cindex 列索引（可以使用列索引，也可以使用列名）
' @optional-param needCreate 如果描述信息对象不存在是否需要进行创建。默认值=False
' @return IDFDescriptor 数据描述器
Public Function IDataFrame_getDataDescriptor(ByRef rindex As Long, ByRef cindex As Variant, Optional ByRef needCreate As Boolean = False) As IDFDescriptor
    ' 1. 获取真实的列对象
    Dim col As IDFSeries
    Set col = delegatedDF.getCol(fieldColumnIndexes(cindex))

    ' 2. 检查行索引的范围
    If rindex < 0 Or rindex >= fieldSize Then
        Exit Function
    End If

On Error Goto catch:
    ' 3. 获取指定行的描述器
    Set IDataFrame_getDataDescriptor = col.getDataDescriptor(fieldRowIndexes(rindex), needCreate)
    Exit Function
catch:
    ' 处理列索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10114, Description:="parameter rindex=" & rindex & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Function

'--------------------------------------------------
' 数据操作
'--------------------------------------------------
' 读数据
' 使用列索引定位
'
' @param rIndex 行索引
' @param cIndex 列索引
Public Function IDataFrame_getDataByColIndex(ByRef rIndex As Long, ByRef cIndex As Integer) As Variant
On Error Goto catch:
    IDataFrame_getDataByColIndex = delegatedDF.getDataByColIndex( _
        fieldRowIndexes(rIndex), _
        fieldColumnIndexes(cIndex) _
    )
    Exit Function
catch:
    ' 处理列索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10219, Description:="index out of bounds." & vbCrLf & _
            "rIndex=" & rIndex & ", min index=0, max index=" & (fieldSize - 1) & "." & vbCrLf & _
            "cIndex=" & cIndex & ", min index=0, max index=" & (fieldColCount - 1) & "."
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Function

' 读数据
' 使用列索引定位
'
' @param rIndex 行索引
' @param cName 列名
Public Function IDataFrame_getDataByColName(ByRef rIndex As Long, ByRef cName As String) As Variant
    ' 1. 检查列名是否存在
    If columnNameIndexMap.Exists(cName) = False Then
        Err.Raise 10215, Description:="DataFrame do not exist column[" & cName & "]"
        Exit Function
    End If

On Error Goto catch:
    ' 2. 读取数据
    IDataFrame_getDataByColName = delegatedDF.getDataByColName(fieldRowIndexes(rIndex), cName)
    Exit Function
catch:
    ' 处理列索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10213, Description:="parameter rIndex=" & rIndex & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Function

' 写数据
' 使用列索引定位
'
' @param rIndex 行索引
' @param cIndex 列索引
Public Sub IDataFrame_setDataByColIndex(ByRef rIndex As Long, ByRef cIndex As Integer, ByRef val As Variant)
On Error Goto catch:
    delegatedDF.setDataByColIndex _
        fieldRowIndexes(rIndex), _
        fieldColumnIndexes(cIndex), _
        val
    Exit Sub
catch:
    ' 处理列索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10219, Description:="index out of bounds." & _
            "rIndex=" & rIndex & ", min index=0, max index=" & (fieldSize - 1) & "." & _
            "cIndex=" & cIndex & ", min index=0, max index=" & (fieldColCount - 1) & "."
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Sub

' 写数据
' 使用列索引定位
'
' @param rIndex 行索引
' @param cName 列名
Public Sub IDataFrame_setDataByColName(ByRef rIndex As Long, ByRef cName As String, ByRef val As Variant)
    ' 1. 检查列名是否存在
    If columnNameIndexMap.Exists(cName) = False Then
        Err.Raise 10215, Description:="DataFrame do not exist column[" & cName & "]"
        Exit Sub
    End If

On Error Goto catch:
    ' 2. 读取数据
    delegatedDF.setDataByColName fieldRowIndexes(rIndex), cName, val
    Exit Sub
catch:
    ' 处理列索引范围异常，其他异常正常抛出
    If Err.Number = 9 Then
        Err.Raise 10213, Description:="parameter rIndex=" & rIndex & " out of bounds. min index=0, max index[" & (fieldSize - 1) & "]"
    Else
        Err.Raise Err.Number, Description:=Err.Description
    End If
End Sub

'--------------------------------------------------
' 检索操作
'--------------------------------------------------
' 创建一个过滤器，以辅助完成检索操作
Public Function IDataFrame_filter() As DFFilter
    Dim result As DFFilter
    Set result = New DFFilter

    result.Init Me

    Set IDataFrame_filter = result
End Function

'--------------------------------------------------
' 切片操作
'--------------------------------------------------
' 切片
' 同时切行 + 列
' 如:
'     df.slice(1,3,1, 2,4,2)
'     df.slice(1,3,1, [1,2,3,4])
'     df.slice(1,3,1, ["a","b","c","d"])
'     df.slice([1,2,3,4], ["a","b","c","d"])
'     df.slice([1,2,3,4], 1,3,1)
'
' @optional-param rowStart 行起始位置
' @optional-param rowStop  行终止位置(最终不会包含 stop 的索引)
' @optional-param rowStep  行起始位置
' @optional-param colStart 列起始位置
' @optional-param colStop 列终止位置(最终不会包含 stop 的索引)
' @optional-param colStep 列起始位置
' @optional-param rowIndexes 行索引数组
'                            如果使用了 rowIndexes, 将会忽略 rowStart + rowStop + rowStep 这三个参数
' @optional-param colIndexes 列索引数组（可以使用数值索引，也可以使用列名字符串）
'                            如果使用了 rowIndexes, 将会忽略 colStart + colStop + colStep 这三个参数
Public Function IDataFrame_slice( _
    Optional ByRef rowStart As Variant, _
    Optional ByRef rowStop As Variant, _
    Optional ByRef rowStep As Variant=1, _
    Optional ByRef colStart As Variant, _
    Optional ByRef colStop As Variant, _
    Optional ByRef colStep As Variant=1, _
    Optional ByRef rowIndexes As Variant, _
    Optional ByRef colIndexes As Variant _
) As IDataFrame
    Dim result As DataFrameSlice
    Set result = New DataFrameSlice
    result.Init Me, _
        rowStart, rowStop, rowStep, _
        colStart, colStop, colStep, _
        rowIndexes, colIndexes

    Set IDataFrame_slice = result
End Function


'--------------------------------------------------
' 输出操作
'--------------------------------------------------
' 将 DataFrame 中的内容输出到控制台
' 按行输出，每个列之间以【,】分割
' Empty 将会以字符串 "@@EMPTY@@" 的形式输出
'
' @optional-param needColName 是否需要输出所有列名。默认=True
Public Sub IDataFrame_printToConsole(Optional ByRef needColName As Boolean = True)
    ' 1. 如果当前 DF 没有任何列，则立刻结束
    If fieldColCount = 0 Then
        Exit Sub
    End If

    ' 2. 输出列名
    Dim line As String
    Dim j As Integer
    If needColName Then
        line = delegatedDF.colIndexToName(fieldColumnIndexes(0))
        For j = 1 To (fieldColCount - 1)
            line = line & "," & delegatedDF.colIndexToName(fieldColumnIndexes(j))
        Next
        Debug.Print line
    End If

    ' 3. 如果当前 DF 没有任何行，则立刻结束
    If fieldSize = 0 Then
        Exit Sub
    End If

    ' 4. 遍历所有列对象
    Dim i As Long
    Dim temp As Variant
    For i = 0 To fieldSize - 1
        ' 拼接所有列
        temp = delegatedDF.getDataByColIndex(fieldRowIndexes(i), fieldColumnIndexes(0))

        If IsEmpty(temp) Then
            line = DFHelper.EMPTY_STR_IN_CONSOLE
        Else
            line = temp & ""
        End If

        For j = 1 To (fieldColCount - 1)
            temp = delegatedDF.getDataByColIndex(fieldRowIndexes(i), fieldColumnIndexes(j))

            If IsEmpty(temp) Then
                line = line & "," & DFHelper.EMPTY_STR_IN_CONSOLE
            Else
                line = line & "," & temp
            End If
        Next

        ' 输出这一行
        Debug.Print line
    Next
End Sub

' 将 DataFrame 中的内容输出到指定 sheet 中
' 为了保证输出和现实结果相同，所有单元格将会被设置成字符串，并且所有数据将会被转换成字符串后写出
' 为了加速，默认所有的组件将以列为单位写出数据
'
' @param sh 需要写入数据的 sheet 对象
' @optional-param sheetWriteRowNo 数据写入 sheet 对象时的起始行号.
'                                 默认 = 1
'                                 可用类型，Integer 或者 Long
'
'                                 如果行号小于 0，将会自动使用 1
'                                 如果行号大于最大值 1048576, 将会自动使用 1
'                                 从指定的 sheet 行号开始，最多从 DataFrame 中读取并输出 (1048576 - 行号（如果不输出列名，则 + 1）) 行，多余的行将被省略
'
' @optional-param sheetWriteColNo 数据写入 sheet 对象时的起始列号、或者列名.
'                                 默认 = 1
'                                 如果是列号，请使用 Integer 或者 Long
'                                 如果是列名，请使用字符串
'
'                                 如果列号小于 0，将会自动使用 1
'                                 如果列号大于最大值 16384, 将会自动使用 1
'                                 从指定的 sheet 列号开始，最多从 DataFrame 中读取并输出 (16384 - 行号 + 1) 列，多余的列将被省略
' @optional-param needColName 是否需要在第一行输出所有列名。默认=True
' @optional-param dfRowStart 输出的起始行(包括这一行，从 0 开始)
'                          只支持 0、正数，如果输入了一个负数，则自动使用 0
'                          如果 dfRowStart > dfRowEnd，则不输出任何数据
' @optional-param dfRowEnd 输出的结束行(包括这一行，从 0 开始)
'                          只支持 0、正数，如果输入了一个负数，则自动使用最大行索引
' @optional-param rowDescriptorHandler 行描述器的处理方法名
'                                      应该提供一个形如以下形式的 Module 级别方法，来处理每个行描述器
'                                       Sub rowDescriptorMethod(ByRef ctx As DFSheetWriterContext, ByRef descriptor As IDFDescriptor)
'                                           参数 ctx 表示当前正在使用的上下文对象
'                                           参数 descriptor 存在 Nothing 的情况，需要在方法内手动处理
' @optional-param dataDescriptorHandler 行描述器的处理方法名
'                                      应该提供一个形如以下形式的 Module 级别方法，来处理每个数据槽位的描述器
'                                       Sub rowDescriptorMethod(ByRef ctx As DFSheetWriterContext, ByRef descriptor As IDFDescriptor)
'                                           参数 ctx 表示当前正在使用的上下文对象
'                                           参数 descriptor 存在 Nothing 的情况，需要在方法内手动处理
' @optional-param colFormatMap 各列的 Format 字符串集合
'                              Map<ColIndex/"ColName", "Format String">
'                              如果没有设置当前参数，所有数据将会直接输出
Public Sub IDataFrame_printToWorksheet(ByRef sh As Worksheet, _
    Optional ByRef sheetWriteRowNo As Variant = -1, _
    Optional ByRef sheetWriteColNo As Variant = -1, _
    Optional ByRef needColName As Boolean = True, _
    Optional ByRef dfRowStart As Long = -1, _
    Optional ByRef dfRowEnd As Long = -1, _
    Optional ByRef rowDescriptorHandler As String = "", _
    Optional ByRef dataDescriptorHandler As String = "", _
    Optional ByRef colFormatMap As Object _
    )

    Dim wc As DFSheetWriterContext
    Set wc = New DFSheetWriterContext

    wc.Init sh, Me, _
        sheetWriteRowNo, _
        sheetWriteColNo, _
        needColName, _
        dfRowStart, _
        dfRowEnd, _
        rowDescriptorHandler, _
        dataDescriptorHandler, _
        colFormatMap
    wc.writeDF
End Sub

